PowerShell을 사용하여 텍스트 파일을 분할하려면 어떻게 해야 합니까?
큰(500MB) 텍스트 파일(log4net 예외 파일)을 5MB 파일 100개처럼 관리하기 쉬운 청크로 분할해야 합니다.
PowerShell을 위한 산책이 될 것 같습니다.어떻게 해야 하죠?
기존 답변 중 일부에 대한 경고 메시지입니다. 파일이 매우 크면 실행 속도가 매우 느려집니다.1.6GB 로그파일은 몇 시간 후에 포기했습니다.다음 날 업무에 복귀할 때까지 종료되지 않는다는 것을 깨달았습니다.
두 가지 문제: Add-Content 호출은 소스 파일 내의 모든 행에 대해 현재 수신인 파일을 열고 검색한 후 닫습니다.매번 소스 파일을 조금씩 읽고 새로운 행을 찾는 것도 느려지지만, 제 생각에는 Add-Content가 주범인 것 같습니다.
다음의 변형에서는, 행의 중간에 파일이 분할되지만, 1.6 GB 의 로그는 1 분 이내에 분할됩니다.
$from = "C:\temp\large_log.txt"
$rootName = "C:\temp\large_log_chunk"
$ext = "txt"
$upperBound = 100MB
$fromFile = [io.file]::OpenRead($from)
$buff = new-object byte[] $upperBound
$count = $idx = 0
try {
do {
"Reading $upperBound"
$count = $fromFile.Read($buff, 0, $buff.Length)
if ($count -gt 0) {
$to = "{0}.{1}.{2}" -f ($rootName, $idx, $ext)
$toFile = [io.file]::OpenWrite($to)
try {
"Writing $count to $to"
$tofile.Write($buff, 0, $count)
} finally {
$tofile.Close()
}
}
$idx ++
} while ($count -gt 0)
}
finally {
$fromFile.Close()
}
라인 수에 따라 분할할 수 있는 간단한 단일 라이너(이 경우 100):
$i=0; Get-Content .....log -ReadCount 100 | %{$i++; $_ | Out-File out_$i.txt}
표준 Get-Content cmdlet이 매우 큰 파일을 잘 처리하지 못하기 때문에 PowerShell은 이 작업을 다소 쉽게 수행할 수 있습니다.를 사용하는 것이 좋습니다.NET StreamReader 클래스에서 PowerShell 스크립트의 파일을 한 줄씩 읽고Add-Content
파일명의 에 각 .다음과 같이 합니다.
$upperBound = 50MB # calculated by Powershell
$ext = "log"
$rootName = "log_"
$reader = new-object System.IO.StreamReader("C:\Exceptions.log")
$count = 1
$fileName = "{0}{1}.{2}" -f ($rootName, $count, $ext)
while(($line = $reader.ReadLine()) -ne $null)
{
Add-Content -path $fileName -value $line
if((Get-ChildItem -path $fileName).Length -ge $upperBound)
{
++$count
$fileName = "{0}{1}.{2}" -f ($rootName, $count, $ext)
}
}
$reader.Close()
여기의 모든 답변과 동일하지만 StreamReader/StreamWriter를 사용하여 새 행으로 분할합니다(파일 전체를 한 번에 메모리로 읽는 것이 아니라 한 줄씩).이 방법을 사용하면 대용량 파일을 가장 빠르게 분할할 수 있습니다.
주의: 오류 확인은 거의 하지 않기 때문에 고객님의 경우 원활하게 동작할 수 있을지는 장담할 수 없습니다.이 기능은 제 기능을 했습니다(1.7GB TXT 파일, 400만 행이 파일당 100,000 행으로 95초 만에 분할됨).
#split test
$sw = new-object System.Diagnostics.Stopwatch
$sw.Start()
$filename = "C:\Users\Vincent\Desktop\test.txt"
$rootName = "C:\Users\Vincent\Desktop\result"
$ext = ".txt"
$linesperFile = 100000#100k
$filecount = 1
$reader = $null
try{
$reader = [io.file]::OpenText($filename)
try{
"Creating file number $filecount"
$writer = [io.file]::CreateText("{0}{1}.{2}" -f ($rootName,$filecount.ToString("000"),$ext))
$filecount++
$linecount = 0
while($reader.EndOfStream -ne $true) {
"Reading $linesperFile"
while( ($linecount -lt $linesperFile) -and ($reader.EndOfStream -ne $true)){
$writer.WriteLine($reader.ReadLine());
$linecount++
}
if($reader.EndOfStream -ne $true) {
"Closing file"
$writer.Dispose();
"Creating file number $filecount"
$writer = [io.file]::CreateText("{0}{1}.{2}" -f ($rootName,$filecount.ToString("000"),$ext))
$filecount++
$linecount = 0
}
}
} finally {
$writer.Dispose();
}
} finally {
$reader.Dispose();
}
$sw.Stop()
Write-Host "Split complete in " $sw.Elapsed.TotalSeconds "seconds"
1.7 GB 파일을 분할하는 출력:
...
Creating file number 45
Reading 100000
Closing file
Creating file number 46
Reading 100000
Closing file
Creating file number 47
Reading 100000
Closing file
Creating file number 48
Reading 100000
Split complete in 95.6308289 seconds
나는 종종 같은 일을 해야 한다.문제는 헤더를 각 분할 청크로 반복하는 것입니다.다음 cmdlet(PowerShell v2 CTP 3)을 작성하면 문제가 없습니다.
##############################################################################
#.SYNOPSIS
# Breaks a text file into multiple text files in a destination, where each
# file contains a maximum number of lines.
#
#.DESCRIPTION
# When working with files that have a header, it is often desirable to have
# the header information repeated in all of the split files. Split-File
# supports this functionality with the -rc (RepeatCount) parameter.
#
#.PARAMETER Path
# Specifies the path to an item. Wildcards are permitted.
#
#.PARAMETER LiteralPath
# Specifies the path to an item. Unlike Path, the value of LiteralPath is
# used exactly as it is typed. No characters are interpreted as wildcards.
# If the path includes escape characters, enclose it in single quotation marks.
# Single quotation marks tell Windows PowerShell not to interpret any
# characters as escape sequences.
#
#.PARAMETER Destination
# (Or -d) The location in which to place the chunked output files.
#
#.PARAMETER Count
# (Or -c) The maximum number of lines in each file.
#
#.PARAMETER RepeatCount
# (Or -rc) Specifies the number of "header" lines from the input file that will
# be repeated in each output file. Typically this is 0 or 1 but it can be any
# number of lines.
#
#.EXAMPLE
# Split-File bigfile.csv 3000 -rc 1
#
#.LINK
# Out-TempFile
##############################################################################
function Split-File {
[CmdletBinding(DefaultParameterSetName='Path')]
param(
[Parameter(ParameterSetName='Path', Position=1, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[String[]]$Path,
[Alias("PSPath")]
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[String[]]$LiteralPath,
[Alias('c')]
[Parameter(Position=2,Mandatory=$true)]
[Int32]$Count,
[Alias('d')]
[Parameter(Position=3)]
[String]$Destination='.',
[Alias('rc')]
[Parameter()]
[Int32]$RepeatCount
)
process {
# yeah! the cmdlet supports wildcards
if ($LiteralPath) { $ResolveArgs = @{LiteralPath=$LiteralPath} }
elseif ($Path) { $ResolveArgs = @{Path=$Path} }
Resolve-Path @ResolveArgs | %{
$InputName = [IO.Path]::GetFileNameWithoutExtension($_)
$InputExt = [IO.Path]::GetExtension($_)
if ($RepeatCount) { $Header = Get-Content $_ -TotalCount:$RepeatCount }
# get the input file in manageable chunks
$Part = 1
Get-Content $_ -ReadCount:$Count | %{
# make an output filename with a suffix
$OutputFile = Join-Path $Destination ('{0}-{1:0000}{2}' -f ($InputName,$Part,$InputExt))
# In the first iteration the header will be
# copied to the output file as usual
# on subsequent iterations we have to do it
if ($RepeatCount -and $Part -gt 1) {
Set-Content $OutputFile $Header
}
# write this chunk to the output file
Write-Host "Writing $OutputFile"
Add-Content $OutputFile $_
$Part += 1
}
}
}
}
단일 vCard VCF 파일의 여러 연락처를 다른 파일로 분할하려고 하다가 이 질문을 발견했습니다.리의 코드를 바탕으로 한 일은 이렇습니다새로운 StreamReader 개체를 만드는 방법을 찾아봐야 했고 null을 $null로 변경했습니다.
$reader = new-object System.IO.StreamReader("C:\Contacts.vcf")
$count = 1
$filename = "C:\Contacts\{0}.vcf" -f ($count)
while(($line = $reader.ReadLine()) -ne $null)
{
Add-Content -path $fileName -value $line
if($line -eq "END:VCARD")
{
++$count
$filename = "C:\Contacts\{0}.vcf" -f ($count)
}
}
$reader.Close()
이러한 답변의 대부분은 소스 파일에 비해 너무 느렸습니다.소스 파일은 10MB에서 800MB 사이의 SQL 파일로 줄 수가 거의 같은 파일로 분할해야 했습니다.
Add-Content를 사용하는 이전 답변 중 일부는 매우 느리다는 것을 알게 되었습니다.스플릿이 끝나기 위해 몇 시간을 기다리는 것은 드문 일이 아니었다.
Typhlosaurus의 답변은 시도하지 않았지만 줄 세기가 아닌 파일 크기로만 분할하는 것 같습니다.
제 목적에 맞는 것은 다음과 같습니다.
$sw = new-object System.Diagnostics.Stopwatch
$sw.Start()
Write-Host "Reading source file..."
$lines = [System.IO.File]::ReadAllLines("C:\Temp\SplitTest\source.sql")
$totalLines = $lines.Length
Write-Host "Total Lines :" $totalLines
$skip = 0
$count = 100000; # Number of lines per file
# File counter, with sort friendly name
$fileNumber = 1
$fileNumberString = $filenumber.ToString("000")
while ($skip -le $totalLines) {
$upper = $skip + $count - 1
if ($upper -gt ($lines.Length - 1)) {
$upper = $lines.Length - 1
}
# Write the lines
[System.IO.File]::WriteAllLines("C:\Temp\SplitTest\result$fileNumberString.txt",$lines[($skip..$upper)])
# Increment counters
$skip += $count
$fileNumber++
$fileNumberString = $filenumber.ToString("000")
}
$sw.Stop()
Write-Host "Split complete in " $sw.Elapsed.TotalSeconds "seconds"
54MB 파일의 경우 출력은...
Reading source file...
Total Lines : 910030
Split complete in 1.7056578 seconds
제 요건에 맞는 심플한 라인 기반의 분할 스크립트를 찾고 있는 다른 사용자가 이 스크립트를 유용하게 사용할 수 있기를 바랍니다.
빠른(그리고 다소 지저분한) 원라이너도 있습니다.
$linecount=0; $i=0; Get-Content .\BIG_LOG_FILE.txt | %{ Add-Content OUT$i.log "$_"; $linecount++; if ($linecount -eq 3000) {$I++; $linecount=0 } }
하드 코딩된 3000 값을 변경하여 배치당 첫 번째 줄 수를 조정할 수 있습니다.
다음을 수행합니다.
파일 1
빠른(그리고 다소 지저분한) 원라이너도 있습니다.
$linecount=0; $i=0;
Get-Content .\BIG_LOG_FILE.txt | %
{
Add-Content OUT$i.log "$_";
$linecount++;
if ($linecount -eq 3000) {$I++; $linecount=0 }
}
하드 코딩된 3000 값을 변경하여 배치당 첫 번째 줄 수를 조정할 수 있습니다.
Get-Content C:\TEMP\DATA\split\splitme.txt | Select -First 5000 | out-File C:\temp\file1.txt -Encoding ASCII
파일 2
Get-Content C:\TEMP\DATA\split\splitme.txt | Select -Skip 5000 | Select -First 5000 | out-File C:\temp\file2.txt -Encoding ASCII
파일 3
Get-Content C:\TEMP\DATA\split\splitme.txt | Select -Skip 10000 | Select -First 5000 | out-File C:\temp\file3.txt -Encoding ASCII
기타...
각 부품의 크기에 따라 파일을 분할할 수 있도록 조금 수정했습니다.
##############################################################################
#.SYNOPSIS
# Breaks a text file into multiple text files in a destination, where each
# file contains a maximum number of lines.
#
#.DESCRIPTION
# When working with files that have a header, it is often desirable to have
# the header information repeated in all of the split files. Split-File
# supports this functionality with the -rc (RepeatCount) parameter.
#
#.PARAMETER Path
# Specifies the path to an item. Wildcards are permitted.
#
#.PARAMETER LiteralPath
# Specifies the path to an item. Unlike Path, the value of LiteralPath is
# used exactly as it is typed. No characters are interpreted as wildcards.
# If the path includes escape characters, enclose it in single quotation marks.
# Single quotation marks tell Windows PowerShell not to interpret any
# characters as escape sequences.
#
#.PARAMETER Destination
# (Or -d) The location in which to place the chunked output files.
#
#.PARAMETER Size
# (Or -s) The maximum size of each file. Size must be expressed in MB.
#
#.PARAMETER RepeatCount
# (Or -rc) Specifies the number of "header" lines from the input file that will
# be repeated in each output file. Typically this is 0 or 1 but it can be any
# number of lines.
#
#.EXAMPLE
# Split-File bigfile.csv -s 20 -rc 1
#
#.LINK
# Out-TempFile
##############################################################################
function Split-File {
[CmdletBinding(DefaultParameterSetName='Path')]
param(
[Parameter(ParameterSetName='Path', Position=1, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[String[]]$Path,
[Alias("PSPath")]
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[String[]]$LiteralPath,
[Alias('s')]
[Parameter(Position=2,Mandatory=$true)]
[Int32]$Size,
[Alias('d')]
[Parameter(Position=3)]
[String]$Destination='.',
[Alias('rc')]
[Parameter()]
[Int32]$RepeatCount
)
process {
# yeah! the cmdlet supports wildcards
if ($LiteralPath) { $ResolveArgs = @{LiteralPath=$LiteralPath} }
elseif ($Path) { $ResolveArgs = @{Path=$Path} }
Resolve-Path @ResolveArgs | %{
$InputName = [IO.Path]::GetFileNameWithoutExtension($_)
$InputExt = [IO.Path]::GetExtension($_)
if ($RepeatCount) { $Header = Get-Content $_ -TotalCount:$RepeatCount }
Resolve-Path @ResolveArgs | %{
$InputName = [IO.Path]::GetFileNameWithoutExtension($_)
$InputExt = [IO.Path]::GetExtension($_)
if ($RepeatCount) { $Header = Get-Content $_ -TotalCount:$RepeatCount }
# get the input file in manageable chunks
$Part = 1
$buffer = ""
Get-Content $_ -ReadCount:1 | %{
# make an output filename with a suffix
$OutputFile = Join-Path $Destination ('{0}-{1:0000}{2}' -f ($InputName,$Part,$InputExt))
# In the first iteration the header will be
# copied to the output file as usual
# on subsequent iterations we have to do it
if ($RepeatCount -and $Part -gt 1) {
Set-Content $OutputFile $Header
}
# test buffer size and dump data only if buffer is greater than size
if ($buffer.length -gt ($Size * 1MB)) {
# write this chunk to the output file
Write-Host "Writing $OutputFile"
Add-Content $OutputFile $buffer
$Part += 1
$buffer = ""
} else {
$buffer += $_ + "`r"
}
}
}
}
}
}
UNIX 명령어 분할 작업처럼 들립니다.
split MyBigFile.csv
55GB의 CSV 파일을 21k 청크로 10분 이내에 분할할 수 있습니다.
PowerShell의 네이티브는 아니지만, 예를 들어 git for Windows 패키지 https://git-scm.com/download/win가 포함되어 있습니다.
로그에서는 행이 가변적일 수 있기 때문에 파일마다 여러 행을 사용하는 것이 가장 좋다고 생각했습니다.다음 코드 조각은 4백만 줄의 로그 파일을 19초 이내에 처리했습니다(18.83).seconds)를 500,000 행 청크로 분할합니다.
$sourceFile = "c:\myfolder\mylargeTextyFile.csv"
$partNumber = 1
$batchSize = 500000
$pathAndFilename = "c:\myfolder\mylargeTextyFile part $partNumber file.csv"
[System.Text.Encoding]$enc = [System.Text.Encoding]::GetEncoding(65001) # utf8 this one
$fs=New-Object System.IO.FileStream ($sourceFile,"OpenOrCreate", "Read", "ReadWrite",8,"None")
$streamIn=New-Object System.IO.StreamReader($fs, $enc)
$streamout = new-object System.IO.StreamWriter $pathAndFilename
$line = $streamIn.readline()
$counter = 0
while ($line -ne $null)
{
$streamout.writeline($line)
$counter +=1
if ($counter -eq $batchsize)
{
$partNumber+=1
$counter =0
$streamOut.close()
$pathAndFilename = "c:\myfolder\mylargeTextyFile part $partNumber file.csv"
$streamout = new-object System.IO.StreamWriter $pathAndFilename
}
$line = $streamIn.readline()
}
$streamin.close()
$streamout.close()
이 파일은 파라미터가 있는 함수 또는 스크립트 파일로 쉽게 변환하여 보다 다양한 용도로 사용할 수 있습니다. it용사 it a a를 합니다.StreamReader
★★★★★★★★★★★★★★★★★」StreamWriter
와
나의 요구는 조금 달랐다.콤마 구분 ASCII 파일과 탭 구분 ASCII 파일로 작업하는 경우가 많습니다.이 파일에서는, 1 행이 데이터의 1 레코드입니다.또한 매우 크기 때문에 (헤더 행을 보존하는) 관리하기 쉬운 부분으로 분할해야 합니다.
그래서 기존의 VBScript 방식으로 되돌아가 모든 Windows 컴퓨터에서 실행할 수 있는 작은 .vbs 스크립트를 정리했습니다(WScript에 의해 자동으로 실행됩니다).exe 호스트 엔진을 Windows에서 스크립트로 사용합니다).
이 방법의 장점은 텍스트 스트림을 사용하기 때문에 기본 데이터가 메모리에 로드되지 않는다는 것입니다(또는 적어도 한 번에 모두 로드되는 것은 아닙니다).그 결과 매우 빠르고 실행에 많은 메모리가 필요하지 않습니다.i7에서 이 스크립트를 사용하여 분할한 테스트 파일은 파일 크기가 약 1GB이고 텍스트가 약 1,200만 줄이며 25개의 파트 파일(각각 약 50만 줄)로 분할되어 있습니다.처리는 약 2분 정도 걸렸고 어떤 시점에서도 3MB를 넘지 않았습니다.
여기서 주의할 점은 텍스트스트림 객체가 한 번에 한 줄씩 처리하기 위해 "ReadLine" 함수를 사용하기 때문에 "lines"(각 레코드는 CRLF로 구분됨)가 있는 텍스트파일에 의존한다는 것입니다.그러나 TSV 또는 CSV 파일을 사용하는 경우에는 매우 적합합니다.
Option Explicit
Private Const INPUT_TEXT_FILE = "c:\bigtextfile.txt"
Private Const REPEAT_HEADER_ROW = True
Private Const LINES_PER_PART = 500000
Dim oFileSystem, oInputFile, oOutputFile, iOutputFile, iLineCounter, sHeaderLine, sLine, sFileExt, sStart
sStart = Now()
sFileExt = Right(INPUT_TEXT_FILE,Len(INPUT_TEXT_FILE)-InstrRev(INPUT_TEXT_FILE,".")+1)
iLineCounter = 0
iOutputFile = 1
Set oFileSystem = CreateObject("Scripting.FileSystemObject")
Set oInputFile = oFileSystem.OpenTextFile(INPUT_TEXT_FILE, 1, False)
Set oOutputFile = oFileSystem.OpenTextFile(Replace(INPUT_TEXT_FILE, sFileExt, "_" & iOutputFile & sFileExt), 2, True)
If REPEAT_HEADER_ROW Then
iLineCounter = 1
sHeaderLine = oInputFile.ReadLine()
Call oOutputFile.WriteLine(sHeaderLine)
End If
Do While Not oInputFile.AtEndOfStream
sLine = oInputFile.ReadLine()
Call oOutputFile.WriteLine(sLine)
iLineCounter = iLineCounter + 1
If iLineCounter Mod LINES_PER_PART = 0 Then
iOutputFile = iOutputFile + 1
Call oOutputFile.Close()
Set oOutputFile = oFileSystem.OpenTextFile(Replace(INPUT_TEXT_FILE, sFileExt, "_" & iOutputFile & sFileExt), 2, True)
If REPEAT_HEADER_ROW Then
Call oOutputFile.WriteLine(sHeaderLine)
End If
End If
Loop
Call oInputFile.Close()
Call oOutputFile.Close()
Set oFileSystem = Nothing
Call MsgBox("Done" & vbCrLf & "Lines Processed:" & iLineCounter & vbCrLf & "Part Files: " & iOutputFile & vbCrLf & "Start Time: " & sStart & vbCrLf & "Finish Time: " & Now())
이게 도움이 된다면, 나한테는 딱 맞아.
스크립트는 폴더를 체크하고 모든 CSV 파일을 해석하며 파일당 nb 행의 체크를 수행합니다.파일에 55,000 행이 넘는 파일이 포함되어 있는 경우는, 파일을 500,000 행의 서브 파일로 분할해, 「_1, _2, ...」라고 이름을 붙입니다.스크립트의 마지막에 원래의 파일의 이름이 변경되어 로드가 회피됩니다.
foreach ($MyFile in $MyFolder)
{
# Read parent CSV
$InputFilename = $MyFile
$InputFile = Get-Content $MyFile
$OutputFilenamePattern = "$MyFile"+"_"
Write-Host ".........."
Write-Host ". File to process"
Write-Host ".........."
WRITE-HOST "$MyVar_file_Path"
Write-Host "$InputFilename"
Write-Host "$OutputFilenamePattern"
Write-Host ".........."
$LineLimit = 50000
# Initialize
$line = 0
$i = 0
$file = 0
$start = 0
$nb_lines = (Get-Content $MyFile).Length
Write-Host ".........."
Write-Host "$nb_lines lines in the file"
Write-Host ".........."
if ($nb_lines -gt 55000)
{
# Loop all text lines
while ($line -le $InputFile.Length)
{
# Generate child CSVs
if ($i -eq $LineLimit -Or $line -eq $InputFile.Length)
{
$file++
$Filename = "$OutputFilenamePattern$file.csv"
# $InputFile[0] | Out-File $Filename -Force # Writes Header at the beginning of the line.
If ($file -ne 1) {$InputFile[0] | Out-File $Filename -Force}
$InputFile[$start..($line - 1)] | Out-File $Filename -Force -Append # Original line 19 with the addition of -Append so it doesn't overwrite the headers you just wrote.
# $InputFile[$start..($line-1)] | Out-File $Filename -Force
$start = $line;
$i = 0
Write-Host "$Filename"
}
# Increment counters
$i++;
$line++
}
$Source_name = $MyVar_file_Path2 + "\" + $InputFilename
$Destination_name = $MyVar_file_Path2 + "\" + "Splitted_" + $InputFilename
Write-Host ".........."
Write-Host ". File to rename"
Write-Host ".........."
Write-Host "$Source_name"
Write-Host "$Destination_name"
Write-Host ".........."
Rename-Item $Source_name -NewName $Destination_name
}
Write-Host "."
Write-Host "."
}
여기에서는 patch6.txt(약 32,000줄)라는 파일을 각각 1000줄의 개별 파일로 분할하는 솔루션을 소개합니다.빠르진 않지만 할 수 있어
$infile = "D:\Malcolm\Test\patch6.txt"
$path = "D:\Malcolm\Test\"
$lineCount = 1
$fileCount = 1
foreach ($computername in get-content $infile)
{
write $computername | out-file -Append $path_$fileCount".txt"
$lineCount++
if ($lineCount -eq 1000)
{
$fileCount++
$lineCount = 1
}
}
언급URL : https://stackoverflow.com/questions/1001776/how-can-i-split-a-text-file-using-powershell
'sourcecode' 카테고리의 다른 글
보안 문자열을 일반 텍스트로 변환 (0) | 2023.04.08 |
---|---|
테이블이 있는 경우 테이블을 드롭하려면 어떻게 해야 합니까? (0) | 2023.04.08 |
PowerShell에서 어셈블리를 로드하는 방법 (0) | 2023.04.08 |
이미지 옆에 텍스트를 수직으로 정렬하시겠습니까? (0) | 2023.04.08 |
CSS를 사용하여 div의 애스펙트비를 유지합니다. (0) | 2023.04.08 |